package ferp.android.activities;

import android.app.Dialog;
import android.content.*;
import android.os.Bundle;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.view.Menu;
import android.view.MenuItem;
import ferp.android.BuildConfig;
import ferp.android.GUI;
import ferp.android.R;
import ferp.android.activities.preferences.Preferences;
import ferp.android.dialogs.PollDialog;
import ferp.android.dialogs.PoolDialog;
import ferp.android.dialogs.ProgressDialog;
import ferp.android.dialogs.RadioListViewDialog;
import ferp.android.engagement.ApplicationRater;
import ferp.android.managers.*;
import ferp.android.services.IResolverService2;
import ferp.android.services.ResolverService2;
import ferp.android.services.ServiceException;
import ferp.android.services.ServiceProxy;
import ferp.android.tasks.center.TaskBidAdd;
import ferp.android.tasks.center.TaskCenterConfigGet;
import ferp.android.tasks.center.TaskGameAdd;
import ferp.android.tasks.local.*;
import ferp.core.game.Game;
import ferp.core.game.Settings;
import ferp.core.game.Statistics;
import ferp.core.log.Log;
import ferp.core.player.Player;
import ferp.core.player.Profile;
import ferp.core.tutorial.Scenario;

import java.util.Arrays;

public class Ferp extends Main implements PollManager.Listener
{
  public void exit(ServiceException e)
  {
    boolean ner = e.isDeadObject() || e.status() == ResolverService2.OUT_OF_MEMORY;
    int dialog = ner ? Dialogs.NOT_ENOUGH_RESOURCES : Dialogs.ALERT_ERROR;

    exit(e, e.getMessage(), dialog, !ner);
  }

  @Override
  public void onBackPressed()
  {
    Game g = game();

    if (g == null)
      super.onBackPressed();
    else
      showDialog(g.replay ? Dialogs.EXIT_CONFIRMATION_REPLAY : Dialogs.EXIT_CONFIRMATION_REGULAR);
  }

  @Override
  public void onResolverRestoreRequested(Game g, Settings settings)
  {
    if (isResolverAvailable())
      new TaskGameStateRestorer(this, g, settings, service).executeSerial();
  }

  @Override
  public void onFastMovingRequested(Game g)
  {
    Profile p = profile();

    if (isResolverAvailable())
      new TaskFastMoving(this, g, p.settings, service).executeSerial();
  }

  @Override
  public void onFastMovingStarted(Game g)
  {
    showDialog(Dialogs.FAST_MOVING);
  }

  @Override
  public void onFastMovingFinished(Game g)
  {
    GUI.tryToDismissDialog(this, Dialogs.FAST_MOVING);

    if (!g.options.isHumanInputRequired())
    {
      Game.log(g, this, "onFastMovingFinished");
      process.now();
    }
  }

  @Override
  public void onAfterDeal(Game g)
  {
    super.onAfterDeal(g);

    // show poll
    if (PollManager.get() != null)
      showDialog(Dialogs.POLL);
  }

  @SuppressWarnings("unchecked")
  public void onFirstHumanBid(Game g, Player human)
  {
    // do not contribute in debug
    if (!BuildConfig.DEBUG)
    {
      Profile profile = profile();

      // do not contribute if "useCenter" not enabled or opponent levels are not "Expert"
      if (profile.useCenter && !profile.hacked && profile.centerId > 0 && profile.settings.levels[0].ordinal() == 0 && profile.settings.levels[1].ordinal() == 0)
      {
        Statistics statistics = profile.getStatistics();
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getApplication());
        float ratio = statistics.totalPool > 0 ? (float)statistics.totalAmount / (float)statistics.totalPool : 0;

        // to contribute bids the ratio must be greater than the threshold defined in the remote configuration
        if (ratio > TaskCenterConfigGet.getBidContributionThreshold(preferences))
          new TaskBidAdd(profile, g).execute();
        else
          Log.debug(Log.TAG, "player's score/pool ratio [" + statistics.totalAmount + "/" + statistics.totalPool + "] is lower than threshold");
      }
    }
  }

  @Override
  public void onSetOver(Game g)
  {
    saveOnCloud(g, CloudSave.EVERY_SET);
  }

  @Override
  public void onGameOver(Game g)
  {
    saveOnCloud(g, CloudSave.EVERY_GAME);

    try
    {
      // send results to ferp center
      if (isResolverAvailable())
        TaskGameAdd.execute(this, profile(), ServiceProxy.getSpeed(service));

      // show results
      Results.show(this);
      finish();
    }
    catch (ServiceException e)
    {
      exit(e);
    }
  }

  @Override
  public void onItemSelected(int requestCode, int selected)
  {
    Profile profile = profile();
    Game g = profile.game();

    switch (requestCode)
    {
      case Dialogs.RATE_APPLICATION:
        ApplicationRater.onOptionSelected(this, selected);

        break;

      case Dialogs.POLL:
        // !!! submit is performed automatically
        break;

      case Dialogs.EXIT_CONFIRMATION_REPLAY:
        switch (selected)
        {
          case ExitOptionReplay.CANCEL:
            // protect the case when exit dialog shown twice
            if (g.replay)
            {
              clean();
              profile.stopReplay();

              Game.log(g, this, "REPLAY_CANCEL");
              process.now();
            }

            break;

          case ExitOptionReplay.REPEAT:
            // protect the case when exit dialog shown twice
            if (g.replay)
            {
              clean();
              profile.startReplay();

              Game.log(g, this, "REPLAY_REPEAT");
              process.now();
            }

            break;

          case ExitOptionReplay.EXIT:
            exit();

            break;
        }

        break;
    }
  }

  @Override
  public void onSubmitPollResultsStarted()
  {
    showDialog(Dialogs.POLL_SUBMIT);
  }

  @Override
  public void onSubmitPollResultsEnded(boolean success, int bonus)
  {
    GUI.tryToDismissDialog(this, Dialogs.POLL_SUBMIT);
    GUI.toast(this, success ? R.string.poll_submit_success : R.string.poll_submit_failure);

    if (bonus > 0)
      BonusManager.activateForPoll(this, adManager, bonus);
  }

  @Override
  public void onTrickingTenCheckRequested(Game g)
  {
    Profile p = profile();

    if (isResolverAvailable())
      new TaskTrickingTenCheck(this, g, p.settings, service).executeSerial();
  }

  @Override
  public void onTrickingTenCheckStarted(Game g)
  {
    showDialog(Dialogs.TRICKING_10_CHECK);
  }

  @Override
  public void onTrickingTenCheckFinished(Game g)
  {
    GUI.tryToDismissDialog(this, Dialogs.TRICKING_10_CHECK);

    if (!g.options.isHumanInputRequired())
    {
      Game.log(g, this, "onTrickingTenCheckFinished");
      process.now();
    }
  }

  @Override
  public void onResolverInitialize(Game g, Settings settings) throws Game.Error
  {
    if (isResolverAvailable())
      try
      {
        ServiceProxy.initialize(service, g, settings);
      }
      catch (ServiceException e)
      {
        throw new Game.Error(e);
      }
  }

  @Override
  public void onRecalculateOnNextSolveRequested(int trick) throws Game.Error
  {
    if (isResolverAvailable())
      try
      {
        // recalculate the tree
        ServiceProxy.recalculateOnNextSolve(service, trick);
      }
      catch (ServiceException e)
      {
        throw new Game.Error(e);
      }
  }

  public void onResolverAdvance(Game g, Settings settings, int move) throws Game.Error
  {
    super.onResolverAdvance(g, settings, move);

    if (isResolverAvailable())
      try
      {
        ServiceProxy.advance(service, g.hand, g.trick.current);
      }
      catch (ServiceException e)
      {
        throw new Game.Error(e);
      }
  }

  @Override
  public boolean onOptionsItemSelected(MenuItem item)
  {
    switch (item.getItemId())
    {
      case R.id.main_pool:
        PoolDialog.show(this, Dialogs.POOL, true);
        return true;

      case R.id.main_score:
        startActivity(new Intent(this, Results.class));
        return true;

      case R.id.main_settings:
        Intent intent = new Intent(this, Preferences.class);
        Bundle bundle = new Bundle();

        bundle.putBoolean(Preferences.Intent.NEW_GAME, false);
        bundle.putBoolean(Preferences.Intent.ENTRANCE, false);

        intent.putExtras(bundle);

        // start Preference activity
        startActivityForResult(intent, Preferences.REQUEST_CODE);

        return true;

      default:
        return super.onOptionsItemSelected(item);
    }
  }

  @Override
  public boolean onCreateOptionsMenu(Menu menu)
  {
    super.onCreateOptionsMenu(menu);

    menu.findItem(R.id.main_introduction).setVisible(false);

    return true;
  }

  @Override
  public void showIntroduction(Game g, Settings settings) {/* nothing to do */}
  @Override
  public void showNote(Scenario.Note note) {/* nothing to do */}

  @Override
  protected void handleException(Exception e)
  {
    if (e.getCause() instanceof ServiceException)
      exit((ServiceException)e.getCause());
    else
      super.handleException(e);
  }

  @Override
  protected ProfileManager getProfileManager() {return ProfileManager.regular(this);}

  @Override
  protected boolean isResolverAvailable() {return service != null;}

  @Override
  protected boolean isResolving()
  {
    return TaskSerial.isActive(TaskResolver.class);
  }

  @Override
  protected int getBestScore() throws Game.Error
  {
    try
    {
      return ServiceProxy.getBestScore(service);
    }
    catch (ServiceException e)
    {
      throw new Game.Error(e);
    }
  }

  @Override
  protected void resolve(Player mover) throws Game.Error
  {
    // start resolver task
    new TaskResolver(this, service, mover).executeSerial();
  }

  @Override
  protected byte getBestMove(Game g, int depth) throws Game.Error
  {
    try
    {
      return ServiceProxy.getBestMove(service, depth, g.hand, g.trick.current, g.getCurrentPlayerHand().current);
    }
    catch (ServiceException e)
    {
      throw new Game.Error(e);
    }
  }

  @Override
  protected void notifyResolver() {TaskResolver.setGUIReady();}

  @Override
  protected void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);

    // start the resolver
    startService(new Intent(this, ResolverService2.class));

    // report launch
    ApplicationRater.onAppLaunch(this, Dialogs.RATE_APPLICATION);

/*
    try
    {
      PackageInfo info = getPackageManager().getPackageInfo("ferp.android", PackageManager.GET_SIGNATURES);

      for (Signature signature : info.signatures)
      {
        MessageDigest md = MessageDigest.getInstance("SHA");
        md.update(signature.toByteArray());
        Log.debug(Log.TAG, "KeyHash: " + Base64.encodeToString(md.digest(), Base64.DEFAULT));
      }
    } catch (PackageManager.NameNotFoundException e) {

    } catch (NoSuchAlgorithmException e) {

    }
*/
  }

  @Override
  protected void onStart()
  {
    super.onStart();

    // bind to service
    bindService(new Intent(this, ResolverService2.class), connection, Context.BIND_AUTO_CREATE);
  }

  @Override
  protected void onStop()
  {
    super.onStop();

    // unbind the resolver service
    tryToUnbindService();
  }

  @Override
  protected void onDestroy()
  {
    super.onDestroy();

    // cancel all tasks
    TaskSerial.cancel(this);
    // stop the resolver service
    stopService(new Intent(this, ResolverService2.class));
  }

  @Override
  protected Dialog onCreateDialog(int id)
  {
    switch (id)
    {
      case Dialogs.RATE_APPLICATION:
        return new RadioListViewDialog(this, this, id);

      case Dialogs.FAST_MOVING:
        return new ProgressDialog(this).set(R.string.please_wait, R.string.fast_moving);

      case Dialogs.TRICKING_10_CHECK:
        return new ProgressDialog(this).set(R.string.please_wait, R.string.dialog_tricking10_check_message);

      case Dialogs.POLL:
        return PollDialog.create(this, this);

      case Dialogs.POLL_SUBMIT:
        return new ProgressDialog(this).set(R.string.please_wait, R.string.poll_submit_progress);
    }

    return super.onCreateDialog(id);
  }

  @Override
  protected void onPrepareDialog(int id, Dialog dialog)
  {
    super.onPrepareDialog(id, dialog);

    switch (id)
    {
      case Dialogs.RATE_APPLICATION:
        ((RadioListViewDialog)dialog).set(getString(R.string.rate_title), R.string.rate_text, Arrays.asList(getResources().getStringArray(R.array.rate_types)));
        break;

      case Dialogs.POLL:
        ((PollDialog)dialog).prepare();
        break;
    }
  }

  @Override
  protected void clean()
  {
    // cancel all tasks
    TaskSerial.cancel(this);

    super.clean();
  }

  @Override
  protected boolean isGameReadyForResume()
  {
    Log.debug(Log.TAG, "trying to resume game: p/" + stopped + ", r/" + resumed + ", s/" + service + ", dm/" + DeckManager.instance(this).isReady());

    return isResolverAvailable() && super.isGameReadyForResume();
  }

  @Override
  protected void resumeGame(Profile profile)
  {
    // start preallocate task
    new TaskPreallocator(this, service).executeSerial();

    super.resumeGame(profile);
  }

  private void tryToUnbindService()
  {
    if (isResolverAvailable())
    {
      unbindService(connection);

      service = null;
    }
  }

  private void saveOnCloud(Game g, Settings.CloudSave[] cloudSaves)
  {
    Profile profile = profile();

    if (g.replay || profile.hacked)
      return;

    for (Settings.CloudSave cs : cloudSaves)
      if (profile.settings.cloudSave == cs)
      {
        CloudManager.instance(this).save(profile);

        return;
      }
  }

  // exit replay dialog's options
  private static final class ExitOptionReplay
  {
    private static final int CANCEL = 0;
    private static final int REPEAT = 1;
    private static final int EXIT   = 2;
  }

  // predefined cloud save settings options arrays
  private static final class CloudSave
  {
    private static final Settings.CloudSave[] EVERY_SET  = {Settings.CloudSave.EVERY_SET};
    private static final Settings.CloudSave[] EVERY_GAME = {Settings.CloudSave.EVERY_SET, Settings.CloudSave.EVERY_GAME};
  }

  // resolver service interface
  private IResolverService2 service;
  private ServiceConnection connection = new ServiceConnection()
  {
    // called when the connection with the service is established
    public void onServiceConnected(ComponentName className, IBinder binder)
    {
      if (!exiting)
      {
        service = IResolverService2.Stub.asInterface(binder);

        Log.debug(Log.TAG, "service connected");

        // resume game
        if (service == null)
          exit(null, "onServiceConnected: Stub.asInterface returned null!", Dialogs.ALERT_ERROR, true);
        else
          tryToResumeGame();
      }
    }

    // called when the connection with the service disconnects unexpectedly
    public void onServiceDisconnected(ComponentName className)
    {
      Log.debug(Log.TAG, "service has unexpectedly disconnected, thread " + Thread.currentThread().getId());

      service = null;

      exit(null, "onServiceDisconnected", Dialogs.NOT_ENOUGH_RESOURCES, false);
    }
  };
}
